
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       dev_mgr_thread.c
  * @author     baiyang
  * @date       2021-10-30
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include <stdio.h>
#include <string.h>
#include "dev_mgr.h"

#include <common/microbee.h>
#include <common/time/gp_time.h>
#include <common/console/console.h>
/*-----------------------------------macro------------------------------------*/
#ifndef HAL_DEVICE_THREAD_STACK
#define HAL_DEVICE_THREAD_STACK 2048
#endif
/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/

/*----------------------------------variable----------------------------------*/

/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
/**
  * @brief       
  * @param[in]   us  
  * @param[out]  
  * @retval      
  * @note        
  */
rt_tick_t devmgr_tick_from_microseconds(uint32_t us)
{
    rt_tick_t tick;

    tick = RT_TICK_PER_SECOND * (us / 1000000);
    tick += (RT_TICK_PER_SECOND * (us % 1000000) + 999999) / 1000000;

    /* return the calculated tick */
    return tick;
}

/*
  per-bus callback thread
*/
static void devmgr_bus_thread(void *arg)
{
    gp_device_t dev = (struct gp_device *)arg;

    while (true) {
        uint64_t now = time_micros64();
        struct DeviceCallbackInfo *callback;

        // find a callback to run
        for (callback = dev->bus->bus_callbacks; callback; callback = callback->next) {
            if (now >= callback->next_usec) {
                while (now >= callback->next_usec) {
                    callback->next_usec += callback->period_usec;
                }

                // call it with semaphore held
                callback->cb(callback->parameter);
            }
        }

        // work out when next loop is needed
        uint64_t next_needed = 0;
        now = time_micros64();

        for (callback = dev->bus->bus_callbacks; callback; callback = callback->next) {
            if (next_needed == 0 ||
                callback->next_usec < next_needed) {
                next_needed = callback->next_usec;
                if (next_needed < now) {
                    next_needed = now;
                }
            }
        }

        // delay for at most 50ms, to handle newly added callbacks
        uint32_t delay = 50000;
        if (next_needed >= now && next_needed - now < delay) {
            delay = next_needed - now;
        }
        // don't delay for less than 500usec, so one thread doesn't
        // completely dominate the CPU
        if (delay < 500) {
            delay = 500;
        }

        rt_tick_t tick = devmgr_tick_from_microseconds(delay);

        rt_thread_delay(tick);
    }

    return;
}

bool devmgr_thread_create(gp_device_t dev, const char *name)
{
    rt_err_t res = RT_ERROR;
    rt_uint8_t  priority = RT_THREAD_PRIORITY_MAX - 1;

    switch (devmgr_get_bus_type(dev)) {
    case BUS_TYPE_SPI:
        priority = PRIORITY_SPI;
        break;

    case BUS_TYPE_I2C:
        priority = PRIORITY_I2C;
        break;
    default:
        break;
    }

    dev->bus->thread_ctx = rt_thread_create(name, devmgr_bus_thread, (void *)dev, HAL_DEVICE_THREAD_STACK, priority, 1);

    if (dev->bus->thread_ctx != NULL) {
        res = rt_thread_startup(dev->bus->thread_ctx);
    }

    return res == RT_EOK;
}

/**
  * @brief       
  * @param[in]   dev  
  * @param[in]   period_usec  
  * @param[in]   cb  
  * @param[out]  
  * @retval      
  * @note        
  */
struct DeviceCallbackInfo *devmgr_register_periodic_callback(gp_device_t dev, uint32_t period_usec, void (*cb)(void *parameter), void *parameter)
{
    RT_ASSERT(dev != RT_NULL);

    struct DeviceCallbackInfo *callback = (struct DeviceCallbackInfo *)rt_malloc(sizeof(struct DeviceCallbackInfo));

    if (callback == NULL) {
        return NULL;
    }

    /* clean memory data of object */
    rt_memset(callback, 0, sizeof(struct DeviceCallbackInfo));

    callback->cb = cb;
    callback->parameter = parameter;
    callback->period_usec = period_usec;
    callback->next_usec = time_micros64() + period_usec;
    callback->bus       = dev;

    // add to linked list of callbacks on thread
    callback->next = dev->bus->bus_callbacks;
    dev->bus->bus_callbacks = callback;

    if (!dev->bus->thread_started) {
        dev->bus->thread_started = true;

        // setup a name for the thread
        const uint8_t name_len = 7;
        char *name = (char *)rt_malloc(name_len);

        switch (devmgr_get_bus_type(dev)) {
        case BUS_TYPE_I2C:
            snprintf(name, name_len, "I2C%d",
                     dev->bus->bus_id);
            break;

        case BUS_TYPE_SPI:
            snprintf(name, name_len, "SPI%d",
                     dev->bus->bus_id);
            break;
        default:
            break;
        }

        if (!devmgr_thread_create(dev, name)) {
            console_panic("Failed to create bus thread %s", name);
        }
    }

    return callback;
}

/*
 * Adjust the timer for the next call: it needs to be called from the bus
 * thread, otherwise it will race with it
 */
bool devmgr_adjust_periodic_callback(gp_device_t dev, struct DeviceCallbackInfo *callback, uint32_t period_usec)
{
    if (rt_thread_self() != dev->bus->thread_ctx) {
        return false;
    }

    callback->period_usec = period_usec;
    callback->next_usec = time_micros64() + period_usec;

    return true;
}

/*------------------------------------test------------------------------------*/


